OneShell

I fight for a brighter tomorrow

0%

[afl-training] harness

[afl-training] harness

harness 的名词含义是马具,动词含义是给马套上马具,引申为利用,治理。此处的意思或许应该理解为,如何写好一个 harness(马具)来使用 AFL。

之前在做 quickstart 的时候,没有使用 afl-clang-fast 进行编译,而是使用的 afl-clang,刚刚搜到 afl-clang-fast 的话,需要编译 llvm-mode。

此次的章节是讲的如何写 harness 让 AFL 测试代码片段。如果对 AFL 如何将数据发送到目标程序执行比较熟悉的话,可以跳过这一个章节,直接到 challenge 进行实战,如下的图描述了 AFL 的基本流程和模块间的关系。

  • input:input 文件夹存放初始的种子,高质量的种子文件非常重要
  • queue:从 queue 中读取内容作为程序输入,如果突变后的输入可以触发新的状态变化,将变异后的输入重新放入 queue 中
  • crash:crash 存放触发 crashes 的输入

undifined

在 library.h 的这个库中,主要的功能是提供输入数据并计算得到输出。假如要测试如下的代码,该如何进行测试?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>

#include "library.h"

void lib_echo(char *data, ssize_t len){
if(strlen(data) == 0) {
return;
}
char *buf = calloc(1, len);
strncpy(buf, data, len);
printf("%s",buf);
free(buf);

// A crash so we can tell the harness is working for lib_echo
if(data[0] == 'p') {
if(data[1] == 'o') {
if(data[2] =='p') {
if(data[3] == '!') {
assert(0);
}
}
}
}
}

int lib_mul(int x, int y){
if(x%2 == 0) {
return y << x;
} else if (y%2 == 0) {
return x << y;
} else if (x == 0) {
return 0;
} else if (y == 0) {
return 0;
} else {
return x * y;
}
}

fuzz 库的单输入函数

fuzz 需要的准备工作有以下三点:

  1. 代码是可以正常运行的
  2. 需要插桩,来让 AFL 进行高效运行
  3. 需要将 fuzzer 生成的数据送入到测试库中,因此,我们必须写一个程序将外部输入送入到测试库中,这可以直接从文件中读取或者从标准输入中读取。

为了测试 library.h 库中的函数,那么额外写一个文件 harness.c,其中 main 函数来调用库中的函数,如下:

1
2
3
4
5
6
7
8
#include "library.h"
#include <string.h>
#include <stdio.h>
int main() {
char *data = "Some input data\n";
lib_echo(data, strlen(data));
printf("%d\n", lib_mul(1,2));
}

然后使用下面的命令进行编译,可以看到一共在 20 个地方进行了插桩:

1
AFL_HARDEN=1 afl-clang-fast harness.c library.c -o harness

undifined

创建 input 文件夹,并在其中提供初始化的种子文件,如果就按照上面编译的方式直接 afl-fuzz,会发现 AFL 提示报错:odd, check syntax!

undifined

在 harness 可执行文件中,调用了 library.h 库中的函数,但是没有设置 hook 使得 AFL 变异产生的数据输入到目标库函数中,因此,运行 afl-fuzz 就会抛出一个警告,没有发生任何的事!因此,我们需要修改 harness 代码,使其从标准输入 STDIN 中获取输入,并且将输入数据喂给目标函数,将 harness.c 修改如下:

新增了 read 函数从标准输入 STDIN 中读取数据到缓冲区 input 中,然后喂给 lib_echo 函数运行,也就是对 lib_echo 函数进行 fuzz:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <unistd.h>
#include <string.h>
#include <stdio.h>

#include "library.h"

// fixed size buffer based on assumptions about the maximum size that is likely necessary to exercise all aspects of the target function
#define SIZE 50

int main() {
// make sure buffer is initialized to eliminate variable behaviour that isn't dependent on the input.
char input[SIZE] = {0};

ssize_t length;
length = read(STDIN_FILENO, input, SIZE);

lib_echo(input, length);
}

然后重新使用 afl-clang-fast 插桩,再使用 afl-fuzz 运行,现在就可以正常被 fuzz 了,并且产生了 crashes。明确一点,AFL 产生的输入是直接通过标准输入 STDIN 传递。

undifined

fuzz 库的任意输入函数

如果要测试 lib_mul(int x, int y) 函数,这个函数需要两个输入,而且是两个数字。作者提供的 harness 如下,通过两个 read 函数从 STDIN 标准输入中来传递数据到 harness 可执行文件中。代码修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <unistd.h>
#include <string.h>
#include <stdio.h>

#include "library.h"

// fixed size buffer based on assumptions about the maximum size that is likely necessary to exercise all aspects of the target function
#define SIZE 100

int main(int argc, char* argv[]) {
if((argc == 2) && strcmp(argv[1], "echo") == 0) {
// make sure buffer is initialized to eliminate variable behaviour that isn't dependent on the input.
char input[SIZE] = {0};

ssize_t length;
length = read(STDIN_FILENO, input, SIZE);

lib_echo(input, length);
} else if ((argc == 2) && strcmp(argv[1], "mul") == 0) {
int a,b = 0;
read(STDIN_FILENO, &a, 4);
read(STDIN_FILENO, &b, 4);
printf("%d\n", lib_mul(a,b));
} else {
printf("Usage: %s mul|echo\n", argv[0]);
}
}

重新使用 afl-clang-fast 编译,使用 afl-fuzz 进行 fuzz,但是这个时候需要带参数 mul 运行 harness,并且还需要在初始输入提供一个高质量:两个回车(或者 /0)分隔的整数:

1
2
20
10
1
2
AFL_HARDEN=1 afl-clang-fast harness.c library.c -o harness
afl-fuzz -i in -o out ./harness mul

undifined

应该是 fuzz 不出来什么结果了,因为 lib_mul 函数内部是数字运算,触发不了什么异常。

练习

作者留下了一个练习,如果有一个程序是从 argv 读取文件名,然后读取文件内容到缓冲区,并且将缓冲区传递到目标函数中,那么该如何对这个程序进行 fuzz。

这个部分我没有理解到,是要使用 AFL 构建文件内容然后进行 fuzz 么?